package com.cskaoyan.wordmemorize.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cskaoyan.wordmemorize.auth.StpKit;
import com.cskaoyan.wordmemorize.common.constant.CommonConstant;
import com.cskaoyan.wordmemorize.common.constant.ResultCodeEnum;
import com.cskaoyan.wordmemorize.common.exception.BusinessException;
import com.cskaoyan.wordmemorize.converter.UserConverter;
import com.cskaoyan.wordmemorize.dao.entity.*;
import com.cskaoyan.wordmemorize.dao.mapper.*;
import com.cskaoyan.wordmemorize.dto.admin.LevelPrivilegeDTO;
import com.cskaoyan.wordmemorize.dto.admin.PageDTO;
import com.cskaoyan.wordmemorize.dto.app.*;
import com.cskaoyan.wordmemorize.request.PageRequest;
import com.cskaoyan.wordmemorize.request.UserRemindCommand;
import com.cskaoyan.wordmemorize.service.UserService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class UserServiceImpl implements UserService {

    @Resource
    UserMapper userMapper;

    @Resource
    LevelMapper levelMapper;

    @Resource
    LevelPrivilegeMapper levelPrivilegeMapper;

    @Resource
    UserCheckinStatisticsMapper userCheckinStatisticsMapper;

    @Resource
    UserCheckinMapper userCheckinMapper;
    @Resource
    UserRemindMapper userRemindMapper;

    @Resource
    UserVocBoundStatisticsMapper userVocBoundStatisticsMapper;

    @Resource
    UserVocBoundLogMapper userVocBoundLogMapper;

    @Resource
    UserConverter userConverter;


    @Value("${user.remind.description}")
    String messageRemindDescription;


    @Override
    public UserDTO getUserInfo(Long userId) {
        // 1、查询基本信息
        UserDO userDO = userMapper.selectById(userId);
        // 查询等级对应的特权码

//        LambdaQueryWrapper<LevelDO> levelDOQueryWrapper = new LambdaQueryWrapper<>();
//        levelDOQueryWrapper.eq(LevelDO::getLevelValue, userDO.getLevelValue());
//        LevelDO levelDO = levelMapper.selectOne(levelDOQueryWrapper);

        // 2、根据等级值获取特权码
        LambdaQueryWrapper<LevelPrivilegeDO> levelPrivilegeDOQueryWrapper = new LambdaQueryWrapper<>();
        levelPrivilegeDOQueryWrapper.le(LevelPrivilegeDO::getLevelValue, userDO.getLevelValue())
                .select(LevelPrivilegeDO::getPrivilegeCode);

        List<String> privilegeCodeList = levelPrivilegeMapper.selectList(levelPrivilegeDOQueryWrapper)
                .stream()
                .map(LevelPrivilegeDO::getPrivilegeCode)
                .collect(Collectors.toList());

        userDO.setPrivilegeCodes(privilegeCodeList);
        return userConverter.userDO2DTO(userDO);
    }

    @Transactional
    @Override
    public NextLevelDTO getUserNextDTO(Long userId) {
        // 1、查询基本信息
        UserDO userDO = userMapper.selectById(userId);

        // 2、 得到用户的等级信息
        Integer userLevel = userDO.getLevelValue();
        System.out.println("当前等级 = " + userLevel);

        // 3、获取到用户升级到下一级所需要的天数
        Integer remainDays = 0;
        Integer checkInType = 1;
        if (userLevel == CommonConstant.CHECK_IN_TYPE_MAX_LEVEL) {
            NextLevelDTO nextLevelDTO = new NextLevelDTO();
            nextLevelDTO.setSignType(checkInType);
            nextLevelDTO.setRemainDays(remainDays);
            return nextLevelDTO;
        }
        LevelDO nextLevelDO = getCheckinDayByLevel(userLevel + 1);
        System.out.println("nextLevelDO = " + nextLevelDO);


        if (userLevel < CommonConstant.ACCUMULATE_CHECK_IN_TYPE_MAX_LEVEL) {
            //  4、如果用户的等级小于6级，就按照累计签到天数来升级

            // 4.1、获取到用户签到天数，用户的签到天数 = 用户当前等级对应的签到天数 + 达到当前等级的日志之后的签到天数

            // 获取用户对应等级所需的签到天数
            LevelDO currentLevelDO = getCheckinDayByLevel(userLevel);
            Integer checkInDays = currentLevelDO.getCheckInDays();

            // 获取用户达到当前等级的日志
            LocalDate levelDate = userDO.getLevelDate();
            LocalDate finalCheckinDate = getFinalCheckinDate(userId);


            // 4.2、获取两个日期之间的签到天数
            Integer daysBetween2Dates = finalCheckinDate == null ? 0 : getCheckinDayBetween2Dates(userId, levelDate, finalCheckinDate);

            // 4.3、计算出距离下一级所需签到的天数和下一级签到类型
            remainDays = nextLevelDO.getCheckInDays() - checkInDays - daysBetween2Dates;
            System.out.println("nextLevelDO.getCheckInDays() = " + nextLevelDO.getCheckInDays());
            System.out.println("checkInDays = " + checkInDays);
            System.out.println("daysBetween2Dates = " + daysBetween2Dates);
            checkInType = nextLevelDO.getCheckInType();

        } else if (userLevel < CommonConstant.CHECK_IN_TYPE_MAX_LEVEL) {
            // 5、6-11级如果要升级，就需要查看连续签到天数（和user_checkin表的stick_no有关联）

            // 5.1、此方法是获取用户的最后签到日期
            LocalDate finalCheckinDate = getFinalCheckinDate(userId);
            /* 判断一下最后一次签到的日期：
             *  <1>、如果用户的最后签到日期是前天或者之前，则说明昨天和今天肯定没有签到，此时user_checkin_statistics表中的cur_continuous_days置为0，cur_continuous_start_date置为null
             *  <2>、如果用户的最后签到日期是昨天，则说明今天肯定没有签到，那么user_checkin_statistics表中的cur_continuous_days不发生变化
             *  <3>、如果用户的最后签到日期是今天，说明我已经签到了，那么user_checkin_statistics表中的数据会在我点击签到时发生变化
             *  其实2和3条件一样
             */
            LocalDate today = LocalDate.now();
            System.out.println("最后签到日期finalCheckinDate = " + finalCheckinDate);
            if (finalCheckinDate != null && finalCheckinDate.isBefore(today.minusDays(1))) {
                System.out.println("最后签到日期是前天或者之前");
                LambdaUpdateWrapper<UserCheckinStatisticsDO> userCheckinStatisticsDOUpdateWrapper = new LambdaUpdateWrapper<>();
                userCheckinStatisticsDOUpdateWrapper.eq(UserCheckinStatisticsDO::getUserId, userId)
                        .set(UserCheckinStatisticsDO::getCurContinuousDays, 0)
                        .set(UserCheckinStatisticsDO::getCurContinuousStartDate, null);
                int update = userCheckinStatisticsMapper.update(userCheckinStatisticsDOUpdateWrapper);
                if (update == 0) {
                    throw new BusinessException(ResultCodeEnum.FAIL);
                }
            }

            //----------------------------------------------------------------------------------------------------------------------//
            // 5.2、获取此用户的当前连续签到天数
            LambdaQueryWrapper<UserCheckinStatisticsDO> userCheckinStatisticsDOQueryWrapper = new LambdaQueryWrapper<>();
            userCheckinStatisticsDOQueryWrapper.eq(UserCheckinStatisticsDO::getUserId, userId);
            UserCheckinStatisticsDO userCheckinStatisticsDO = userCheckinStatisticsMapper.selectOne(userCheckinStatisticsDOQueryWrapper);
            Integer curContinuousDays = userCheckinStatisticsDO.getCurContinuousDays();
            LocalDate curContinuousStartDate = userCheckinStatisticsDO.getCurContinuousStartDate();

            // 用户的升级日期（记录用户是哪天升到这么多级的）
            LocalDate levelDate = userDO.getLevelDate();

            // 如果当前连续签到的起始日期 > 用户当前等级升级日期+1, 则根据当前连续签到天数计算等级
            if (curContinuousStartDate == null || levelDate.plusDays(1).isBefore(curContinuousStartDate)) {
                // 这里就说明从用户当前等级升级日期 + 1到昨天为止，用户至少有一天没打卡
                System.out.println("日期没有连起来");
                remainDays = nextLevelDO.getCheckInDays() - curContinuousDays;
            } else {
                // 根据用户当前等级的连续签到数 + (从用户当前等级升级日期 + 1) 到 今天为止的当前连续签到数 计算用户当前的等级
                System.out.println("日期连起来了");
                // 获取用户对应等级所需的连续签到天数
                LevelDO currentLevelDO = getCheckinDayByLevel(userLevel);
                Integer checkInDays = currentLevelDO.getCheckInDays();

                System.out.println("levelDate.plusDays(1) = " + levelDate.plusDays(1));
                long daysBetween = ChronoUnit.DAYS.between(levelDate.plusDays(1), finalCheckinDate);
                remainDays = nextLevelDO.getCheckInDays() - checkInDays - ((int) daysBetween + 1);

                System.out.println("nextLevelDO.getCheckInDays() = " + nextLevelDO.getCheckInDays());
                System.out.println("checkInDays = " + checkInDays);
                System.out.println("daysBetween = " + daysBetween);
            }
            checkInType = nextLevelDO.getCheckInType();

        }

        System.out.println("remainDays = " + remainDays);
        NextLevelDTO nextLevelDTO = userConverter.levelDO2NextLevelDTO(nextLevelDO);
        nextLevelDTO.setRemainDays(remainDays);
        nextLevelDTO.setSignType(checkInType);

        System.out.println("nextLevelDTO = " + nextLevelDTO);
        return nextLevelDTO;
    }

    private LevelDO getCheckinDayByLevel(Integer userLevel) {
        LambdaQueryWrapper<LevelDO> userCheckinDOQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinDOQueryWrapper.eq(LevelDO::getLevelValue, userLevel);
        LevelDO levelDO = levelMapper.selectOne(userCheckinDOQueryWrapper);
        return levelDO;
    }

    /**
     * 获取用户的最后签到日期
     *
     * @param userId
     * @return
     */
    public LocalDate getFinalCheckinDate(Long userId) {
        LambdaQueryWrapper<UserCheckinDO> userCheckinDOQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinDOQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                .orderByDesc(true, UserCheckinDO::getSignDate).last("LIMIT 1");

        UserCheckinDO userCheckinDO = userCheckinMapper.selectOne(userCheckinDOQueryWrapper);
        //从来没有签过到
        if (userCheckinDO == null) {
            return null;
        }
        return userCheckinDO.getSignDate();
    }

    /**
     * 获取最近的签到日期的连续签到天数
     *
     * @param userId
     * @return
     */
    public int getFinalCheckinDays(Long userId) {
        LocalDate finalCheckinDate = getFinalCheckinDate(userId);

        LambdaQueryWrapper<UserCheckinDO> userCheckinDOLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinDOLambdaQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                .eq(UserCheckinDO::getSignDate, finalCheckinDate);

        UserCheckinDO userCheckinDO = userCheckinMapper.selectOne(userCheckinDOLambdaQueryWrapper);
        Long stickNo = userCheckinDO.getStickNo();

        userCheckinDOLambdaQueryWrapper.clear();
        userCheckinDOLambdaQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                .eq(UserCheckinDO::getStickNo, stickNo);

        Long count = userCheckinMapper.selectCount(userCheckinDOLambdaQueryWrapper);

        return Integer.valueOf(count.toString());
    }

    /**
     * 获取两个日期之间的签到天数
     *
     * @param startDate
     * @param endDate
     * @return
     */
    private Integer getCheckinDayBetween2Dates(Long userId, LocalDate startDate, LocalDate endDate) {
        LambdaQueryWrapper<UserCheckinDO> userCheckinDOQueryWrapper = new LambdaQueryWrapper<>();
        userCheckinDOQueryWrapper.eq(UserCheckinDO::getUserId, userId)
                .gt(UserCheckinDO::getSignDate, startDate)
                .le(UserCheckinDO::getSignDate, endDate);

        Long count = userCheckinMapper.selectCount(userCheckinDOQueryWrapper);
        return Integer.valueOf(count.toString());
    }

    @Override
    public DisplayUserLevelDTO getUserLevelInfo(Long userId) {
        // 1、查询基本信息
        UserDO userDO = userMapper.selectById(userId);

        // 2、获取用户已经达到的等级
        LambdaQueryWrapper<LevelDO> levelDOQueryWrapper = new LambdaQueryWrapper<>();
        levelDOQueryWrapper.le(LevelDO::getLevelValue, userDO.getLevelValue());
        List<LevelDO> nowLevelDOS = levelMapper.selectList(levelDOQueryWrapper);
        List<LevelPageDTO> nowLevel = userConverter.levelDOsLevelPageDTOs(nowLevelDOS);

        LambdaQueryWrapper<LevelPrivilegeDO> levelPrivilegeDOQueryWrapper = new LambdaQueryWrapper<>();
        // 3、获取用户已经达到的等级权限集合
        getUserLevelPrivilegeList(levelPrivilegeDOQueryWrapper.le(LevelPrivilegeDO::getLevelValue, userDO.getLevelValue()), nowLevel);

        // 清除当前 LambdaQueryWrapper 对象中的所有查询条件
        levelDOQueryWrapper.clear();

        // 4、获取用户未达到的等级
        levelDOQueryWrapper.gt(LevelDO::getLevelValue, userDO.getLevelValue());
        List<LevelDO> upperLevelDOS = levelMapper.selectList(levelDOQueryWrapper);
        List<LevelPageDTO> upperLevel = userConverter.levelDOsLevelPageDTOs(upperLevelDOS);
        // 5、获取用户未达到的等级权限集合
        getUserLevelPrivilegeList(levelPrivilegeDOQueryWrapper.gt(LevelPrivilegeDO::getLevelValue, userDO.getLevelValue()), upperLevel);

        return DisplayUserLevelDTO.builder().nowLevel(nowLevel).upperLevel(upperLevel).build();
    }

    /**
     * 获取用户的等级权限集合
     *
     * @param levelPrivilegeDOQueryWrapper
     * @param level
     */
    private void getUserLevelPrivilegeList(LambdaQueryWrapper<LevelPrivilegeDO> levelPrivilegeDOQueryWrapper, List<LevelPageDTO> level) {
        List<LevelPrivilegeDO> levelPrivilegeDOS = levelPrivilegeMapper.selectList(levelPrivilegeDOQueryWrapper);
        List<LevelPrivilegeDTO> levelPrivilegeDTOS = userConverter.levelPrivilegeDOs2DTOs(levelPrivilegeDOS);
        for (int i = 0, levelLen = level.size(), levelPrivilegeLen = levelPrivilegeDTOS.size(); i < levelPrivilegeLen; i++) {
            // 因为用户为LVL0时没有特殊权力，而用户必然是从LVL0开始的，所以要跳过
            if (levelLen > levelPrivilegeLen) {
                // 获取用户已经达到的等级权限集合
                level.get(i + 1).setLevelPrivileges(Arrays.asList(levelPrivilegeDTOS.get(i)));
            } else {
                // 获取用户未达到的等级权限集合
                level.get(i).setLevelPrivileges(Arrays.asList(levelPrivilegeDTOS.get(i)));
            }
            levelPrivilegeDOQueryWrapper.clear();
        }
    }

    //更每日单词学习量
    @Override
    public void updateVocCountOfDay(Long userId, Integer count) {
        UpdateWrapper<UserDO> updateWrapper = new UpdateWrapper<>();
        updateWrapper.eq("id", userId)
                .set("voc_count_of_day", count);
        userMapper.update(null, updateWrapper);
    }

    //更新提醒时间
    @Override
    public void updateUserRemind(UserRemindCommand command) {
        Long userId = StpKit.USER.getLoginIdAsLong();
        UserRemindDO userRemindDO = userConverter.userRemindCommand2DO(command);
        userRemindDO.setUserId(userId);
        userRemindMapper.update(
                userRemindDO, new LambdaQueryWrapper<UserRemindDO>().eq(UserRemindDO::getUserId, userId));
    }


    @Override
    public UserRemindDTO getUserRemind(Long userId) {
        return null;
    }


    @Override
    public void messageRemind() {
    }

    //返回用户单词上限变化
    @Override
    public PageDTO<DisplayUserVocBoundLogDTO> getUserVocBoundLog(PageRequest pageRequest, Long userId) {
        if (pageRequest.getPageNum() == null) {
            List<UserVocBoundLogDO> userVocBoundLogDOS = userVocBoundLogMapper.selectList(null);
            return userConverter.userVocBoundLogPage2PageDTO(
                    userConverter.userVocBoundLogDTOs2DisplayDTOs(userVocBoundLogDOS),
                    (long) userVocBoundLogDOS.size()
            );
        }
        Page<UserVocBoundLogDO> pageParam = new Page<>(pageRequest.getPageNum(), pageRequest.getCount());
        LambdaQueryWrapper<UserVocBoundLogDO> userVocBoundLogWrapper = new LambdaQueryWrapper<>();
        userVocBoundLogWrapper.eq(UserVocBoundLogDO::getUserId, userId);
        Page<UserVocBoundLogDO> userVocBoundLogDOPage = userVocBoundLogMapper.selectPage(pageParam, userVocBoundLogWrapper);
        List<DisplayUserVocBoundLogDTO> userVocBoundLogDTOS = userConverter.userVocBoundLogDTOs2DisplayDTOs(userVocBoundLogDOPage.getRecords());
        return userConverter.userVocBoundLogPage2PageDTO(userVocBoundLogDTOS, userVocBoundLogDOPage.getTotal());
    }

    //返回用户单词上限统计
    @Override
    public UserVocBoundStatisticsDTO getUserVocBoundStatistics(Long userId) {
        QueryWrapper<UserVocBoundStatisticsDO> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId);
        UserVocBoundStatisticsDO userVocBoundStatisticsDO = userVocBoundStatisticsMapper.selectOne(wrapper);
        return userConverter.userVocBoundStatisticsDO2DisplayDTO(userVocBoundStatisticsDO);
    }

    //返回用户页面展示条目
    @Override
    public UserPageDTO getUserPage(Long userId) {
        UserDO userDO = userMapper.selectById(userId);
        QueryWrapper<UserVocBoundStatisticsDO> wrapper = new QueryWrapper<>();
        wrapper.eq("user_id", userId);
        QueryWrapper<UserRemindDO> wrapper1 = new QueryWrapper<>();
        wrapper1.eq("user_id", userId);
        UserVocBoundStatisticsDO userVocBoundStatisticsDO = userVocBoundStatisticsMapper.selectOne(wrapper);
        UserRemindDO userRemindDO = userRemindMapper.selectOne(wrapper1);

        UserPageDTO userPageDTO = new UserPageDTO();
        userPageDTO.setVocCountOfDay(userDO.getVocCountOfDay());
        userPageDTO.setTotalVocBound(userVocBoundStatisticsDO.getTotal());
        userPageDTO.setFreeVocBound(userVocBoundStatisticsDO.getFree());
        userPageDTO.setPayVocBound(userVocBoundStatisticsDO.getPay());
        userPageDTO.setReSignCard(userDO.getHasSignCard());
        userPageDTO.setRemindTime(String.valueOf(userRemindDO.getRemindTime()));

        return userPageDTO;
    }


}
